package com.ak.common.aspect;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import com.ak.common.utils.AddressUtil;
import com.ak.server.log.entity.Log;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import com.ak.common.annotation.LogEndpoint;
import com.ak.common.exception.AKException;
import com.ak.common.utils.AKUtil;
import com.ak.common.utils.StringUtil;
import com.ak.server.log.service.ILogService;

import lombok.RequiredArgsConstructor;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author Vean
 */
@Aspect
@Component
@RequiredArgsConstructor
public class LogEndpointAspect {

    private final ILogService logService;
    private final ObjectMapper objectMapper;

    /**
     * 配置切入点（建议注解标记在 Controller 方法上）
     */
    @Pointcut("@annotation(com.ak.common.annotation.LogEndpoint)")
    public void pointcut() {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "pointcut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) throws AKException {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) throws AKException {
        handleLog(joinPoint, e, null);
    }

    protected void handleLog(final JoinPoint point, final Exception e, Object jsonResult)  throws AKException {
        Method targetMethod = resolveMethod(point);
        LogEndpoint annotation = targetMethod.getAnnotation(LogEndpoint.class);
        String operation = annotation.operation();
        long start = System.currentTimeMillis();
        try {
            // result = point.proceed();
            if (StringUtils.isNotBlank(operation)) {
            	String username = AKUtil.getCurrentUsername();
                String ip = AKUtil.getHttpServletRequestIpAddress();
                String ua = StringUtil.sub(AKUtil.getHttpServletRequest().getHeader("user-agent"), 0, 500);

                UserAgent userAgent = UserAgent.parseUserAgentString(ua);
                Browser browser = userAgent.getBrowser();
                OperatingSystem operatingSystem = userAgent.getOperatingSystem();

                Log log = new Log();

                log.setIp(ip);

                log.setUsername(username);
                log.setTime(System.currentTimeMillis() - start);
                log.setOperation(operation);

                String className = point.getTarget().getClass().getName();
                String methodName = targetMethod.getName();
                log.setMethod(className + "." + methodName + "()");

                Object[] args = point.getArgs();
                LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
                Object[] paramNames = u.getParameterNames(targetMethod);
                if (args != null && paramNames != null) {
                    StringBuilder params = new StringBuilder();
                    params = handleParams(params, args, Arrays.asList(paramNames));
                    log.setParams(params.toString());
                }
                log.setCreateTime(new Date());
                log.setLocation(AddressUtil.getCityInfo(ip));

                logService.saveLog(log);
            }
            //return result;
        } catch (Throwable throwable) {
            String exceptionMessage = annotation.exceptionMessage();
            String message = throwable.getMessage();
            String error = StringUtil.containChinese(message) ? exceptionMessage + "，" + message : exceptionMessage;
            throw new AKException(error);
        }
    }

    Method resolveMethod(JoinPoint point) {
        MethodSignature signature = (MethodSignature)point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();
        Method method = getDeclaredMethod(targetClass, signature.getName(), signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName());
        }
        return method;
    }

    private Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethod(superClass, name, parameterTypes);
            }
        }
        return null;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private StringBuilder handleParams(StringBuilder params, Object[] args, List<Object> paramNames) {
        try {
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Map) {
                    Set<Object> set = ((Map) args[i]).keySet();
                    List<Object> list = new ArrayList<>();
                    List<Object> paramList = new ArrayList<>();
                    for (Object key : set) {
                        list.add(((Map) args[i]).get(key));
                        paramList.add(key);
                    }
                    return handleParams(params, list.toArray(), paramList);
                } else {
                    if (args[i] instanceof Serializable) {
                        Class<?> aClass = args[i].getClass();
                        try {
                            aClass.getDeclaredMethod("toString", new Class[]{null});
                            // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ，安全的 writeValueAsString ，否则 走 Object的 toString方法
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                        } catch (NoSuchMethodException e) {
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                        }
                    } else if (args[i] instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) args[i];
                        params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                    } else {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                    }
                }
            }
        } catch (Exception ignore) {
            params.append("参数解析失败");
        }
        return params;
    }
}



